﻿#region License
/* ---------------------------------------------------------------- *
 *
 * This file is part of the API Concepts Framework
 * http://apiconceptsframework.codeplex.com
 *
 * Copyright (C) 2014 eva Kühn, Thomas Scheller, Vienna University of Technology
 * http://www.complang.tuwien.ac.at/eva
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/
 *
 * ---------------------------------------------------------------- */
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using ApiConceptsFramework.Contract;

namespace ApiConceptsFramework.HCs
{
	public class Xml : IHighLevelConcept
	{
		public const int XmlBaseComplexity = 15; //XML seems to influence complexity in general (e.g. Init and Inst tasks in study 2 took significantly longer)
		public const float ElementSearchComplexity = 8;
		public const float ElementUsageComplexity = 13;
		public const float AttributeSearchComplexity = 6;
		public const float AttributeUsageComplexity = 6;
		public const float NoSchemaSearchMultiplicator = 1.5f;
		public const float NoSchemaUsageMultiplicator = 1.5f;
		
		public const int MinNameLengthForSuggestingShorterName = 12;
		//public const int AdditionalComplexityOfLongName = 5;
		
		private readonly LearningEffect _increasedXmlUsageLearningEffect = new LearningEffect(0.5f, 1.5f); //with this the steps are 0.33 and 0.5 (first learning effect higher)
		private readonly InformationLookup _externalInformationLookup = new InformationLookup();

		public Type InputDataType
		{
			get { return typeof(XmlInputData); }
		}

		public HcEvaluationResult Evaluate(List<Assembly> apiAssemblies, IEnumerable<ConceptInstance> data, IHcEvaluationInput hcInputData, IEnumerable<HcEvaluationResult> otherHcData, IDE ide = IDE.VisualStudioOrSimilar)
		{
			if (hcInputData == null)
				return null;
			var pd = hcInputData as XmlInputData;
			if (pd == null)
				throw new ArgumentException("hcInputData must be XmlInputData", "hcInputData");

			return new HcEvaluationResult
				       {
					       HighLevelConcept = this,
					       GeneralComplexity = XmlBaseComplexity,
						   GeneralSuggestions = GetGeneralSuggestions(pd.SchemaAvailable),
					       ComplexityData = GenerateComplexityDataForXmlElements(pd.Elements, pd.SchemaAvailable)
				       };
		}

		private List<string> GetGeneralSuggestions(bool schemaAvailable)
		{
			var suggestions = new List<string>();
			suggestions.Add("XML doesn't have very good usability and should only be used when it is necessary to change the configuration without recompiling. Otherwise consider e.g. using annotations or a fluent interface instead.");
			if (!schemaAvailable)
				suggestions.Add("Consider making a schema available to enable auto completion in the XML.");
			return suggestions;
		}

		private List<HcComplexityData> GenerateComplexityDataForXmlElements(IEnumerable<XmlElement> xmlElements, bool schemaAvailable)
		{
			return xmlElements.Select(e => GenerateComplexityDataForXmlElement(e, schemaAvailable)).ToList();
		}

		private HcComplexityData GenerateComplexityDataForXmlElement(XmlElement xmlElement, bool schemaAvailable)
		{
			return new HcComplexityData
				       {
					       HighLevelConcept = this,
					       Description = "XML element: <" + xmlElement.Name + " " + string.Join(" ", xmlElement.Attributes.Select(a => a.Name)) + " />",
					       SearchComplexity = CalculateSearchComplexity(xmlElement, schemaAvailable),
						   UsageComplexity = CalculateUsageComplexity(xmlElement, schemaAvailable),
						   NumberOfUsages = xmlElement.NumberOfUsages, //usage per attribute, or just per XML element??? -> decide by overall results
						   Suggestions = GetSuggestions(xmlElement, schemaAvailable)
				       };
		}

		private float CalculateSearchComplexity(XmlElement xmlElement, bool schemaAvailable)
		{
			var searchComplexity = CalculateSearchComplexity(ElementSearchComplexity, xmlElement, schemaAvailable) +
				xmlElement.Attributes.Sum(attr => CalculateSearchComplexity(AttributeSearchComplexity, attr, schemaAvailable));
			return searchComplexity;
		}

		private float CalculateSearchComplexity(float baseComplexity, UsedItem item, bool schemaAvailable)
		{
			var searchComplexity = baseComplexity;
			if (!schemaAvailable)
				searchComplexity *= NoSchemaSearchMultiplicator;
			return LearningEffect.Search.CalculateComplexityForMultipleUsages(searchComplexity, item.NumberOfUsages, item.PreviousUsages);
		}

		private float CalculateUsageComplexity(XmlElement xmlElement, bool schemaAvailable)
		{
			var usageComplexity = CalculateUsageComplexity(ElementUsageComplexity, xmlElement, schemaAvailable) +
				xmlElement.Attributes.Sum(attr => CalculateUsageComplexity(AttributeUsageComplexity, attr, schemaAvailable));
			if (xmlElement.NeedsLookupOfExternalInformation)
				usageComplexity += _externalInformationLookup.CalculateCost(xmlElement);
			usageComplexity += xmlElement.Attributes.Where(a => a.NeedsLookupOfExternalInformation).Sum(attr => _externalInformationLookup.CalculateCost(attr));
			return usageComplexity;
		}

		private float CalculateUsageComplexity(float baseComplexity, UsedItem item, bool schemaAvailable)
		{
			var usageComplexity = baseComplexity;
			if (!schemaAvailable)
				usageComplexity *= NoSchemaUsageMultiplicator;
			return _increasedXmlUsageLearningEffect.CalculateComplexityForMultipleUsages(usageComplexity, item.NumberOfUsages, item.PreviousUsages);
		}

		private List<string> GetSuggestions(XmlElement xmlElement, bool schemaAvailable)
		{
			//suggestions for element and attribute names: if name too long, suggest making it shorter
			if (schemaAvailable)
				return null;
			var suggestions = new List<string>();
			foreach (var name in new[]{xmlElement.Name}.Concat(xmlElement.Attributes.Select(a => a.Name)))
			{
				if (name.Length >= MinNameLengthForSuggestingShorterName)
					suggestions.Add("The name " + name + " is rather long, especially when there is no auto completion available - consider making it shorter.");
			}
			return suggestions;
		}
	}
}
